package de.hijacksoft.isledesktopsync.adapter;

import de.hijacksoft.isle.classes.Item;
import de.hijacksoft.isledesktopsync.Globals;
import de.hijacksoft.isledesktopsync.LocalizationManager;
import de.hijacksoft.isledesktopsync.Toast;
import static de.hijacksoft.isledesktopsync.Globals.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;

public class USBDataAdapter {

    public static final int STATUS_ERROR = -1;
    public static final int STATUS_CONNECTING = 0;
    public static final int STATUS_CONNECTED = 1;
    public static final int STATUS_READ_STARTED = 2;
    public static final int STATUS_READ_FINISHED = 3;
    public static final int STATUS_WRITE_STARTED = 4;
    public static final int STATUS_WRITE_FINISHED = 5;
    public static final int STATUS_DISCONNECTED = 6;
    
    // THIS STATUS ONLY EXISTS ON THE PC
    public static final int STATUS_RECIEVE_REQUEST = 7;

    private ArrayList<Item> itemDB = new ArrayList<Item>();
    
    private Socket socket;
    private PrintWriter out;
    private BufferedReader in;
    private Scanner errorScanner;
    private ConnectionHandler handler;
    
    private int status = 0;
    
    private ArrayList<StatusListener> listeners = new ArrayList<StatusListener>();
    
    public void addStatusListener(StatusListener sl){
        listeners.add(sl);
    }
    
    public void removeStatusListener(StatusListener sl){
        listeners.remove(sl);
    }
    
    private void notifyListeners(){
        for(StatusListener sl : listeners) sl.onStatusChange(status, this);
    }
    
    private void setStatus(int status){
        this.status = status;
        notifyListeners();
    }
    
    public void open(){
        handler = new ConnectionHandler();
        handler.initConnection();
    }
    
    private void connect(){
        writeLine(ConnectionHandler.CMD_CONNECTED);
    }
    
    public void close(){

        try{
            writeLine(ConnectionHandler.CMD_CLOSE);
        }
        catch(Exception e){
        }
        
        try {
            handler.close();
            socket.close();
            setStatus(STATUS_DISCONNECTED);
        } catch (Exception ex) {
        }

    }    
        
    /**
     * Initializes the connection to the Android Phone through ADB and USB
     */
    private boolean initConnection() {
        
        socket = null;
        
        try {
            
            socket = new Socket("localhost", Integer.parseInt(PORT_ADBCONNECTION));
            
            out = new PrintWriter(socket.getOutputStream(), true);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            errorScanner = new Scanner(socket.getInputStream());

        } catch (UnknownHostException e) {
            System.err.println("USBDataAdapter: Socket connection problem (Unknown host)" + e.getStackTrace());
            setStatus(STATUS_ERROR);
        } catch (IOException e) {
            System.err.println("USBDataAdapter: Could not initialize I/O on socket " + e.getStackTrace());
            setStatus(STATUS_ERROR);
        } catch (Exception e) {
            System.err.println("USBDataAdapter: Could not initialize I/O on socket " + e.getStackTrace());
        }
        
        
        if(socket != null && socket.isConnected()) {
            
            try{
                String line = in.readLine();                
                if(line == null || line.equals("")) return false;
                if(!line.startsWith(ConnectionHandler.CMD_CONNECTED)) return false;
                else {
                    if(line.replaceAll(ConnectionHandler.CMD_CONNECTED, "").equals(Globals.VERSION)){
                        writeLine(ConnectionHandler.CMD_CONNECTED);
                        setStatus(STATUS_CONNECTED);
                    }
                    else
                        writeLine(ConnectionHandler.CMD_CLOSE);
                }
            }
            catch(Exception ex){
                return false;
            }
            
            return true;
        }
        
        else return false;
        
    }
    
    public void writeLine(String line){
        if(socket != null && socket.isConnected()){
            out.write(line + "\n");
            out.flush();
        }
    }
    
    public void readDatabase(){
        writeLine(ConnectionHandler.CMD_RECIEVE);
    }
    
    private void startReadDatabase(){
        
        if(socket != null && socket.isConnected()){
            ReadThread th = new ReadThread();
            th.start();
        }

    }
    
    public void writeDatabase(ArrayList<Item> itemDB){
        
        if(socket != null && socket.isConnected()){
 
            WriteThread th = new WriteThread(itemDB);
            th.start();
            
        }

    }
        
    public ArrayList<Item> getDatabse(){
        return itemDB;
    }
    
    private class ReadThread extends Thread {
        
        @Override
        public void run(){
            
            handler.stopListening(true);
            setStatus(STATUS_READ_STARTED);
            
            ArrayList<Item> items = new ArrayList<Item>();        
            String line = null;

            try {
                while((line = in.readLine()) != null){
                    
                    // check if sending is complete
                    if(line.equals(ConnectionHandler.CMD_SEND_FINISH)) break;

                    Item addItem = Item.getItemFromString(line);

                    if(addItem != null) items.add(addItem);

                    if(line.equals("")) break;                    

                }
            } catch (IOException ex) {
                System.err.println("USBDataAdapter: I/O error " + ex.getStackTrace());
                setStatus(STATUS_ERROR);
            }
            
            itemDB = items;
            
            setStatus(STATUS_READ_FINISHED);
            handler.stopListening(false);
                        
        }
        
    }
    
    private class WriteThread extends Thread {
        
        ArrayList<Item> itemDB;
        
        public WriteThread(ArrayList<Item> itemDB){
            this.itemDB = itemDB;
        }
        
        @Override
        public void run(){
            
            handler.stopListening(true);
            setStatus(STATUS_WRITE_STARTED);
            
            if(itemDB != null) {

                writeLine(ConnectionHandler.CMD_SEND_START);

                for(Item item : itemDB){
                    writeLine(item.toString());
                }
                
                writeLine(ConnectionHandler.CMD_SEND_FINISH);
                
            }
                        
            setStatus(STATUS_WRITE_FINISHED);
            handler.stopListening(false);
                        
        }
        
    }
    
    class ConnectionHandler implements Runnable {
        
        private static final String CMD_CONNECTED = "CONNECTED";
        private static final String CMD_SEND_START = "SEND_START";
        private static final String CMD_SEND_FINISH = "SEND_FINISH";
        private static final String CMD_RECIEVE = "RECIEVE";
        private static final String CMD_CLOSE = "CLOSE";
        
        private boolean stopListening = false;
        private boolean close = false;
        
        private final Thread idleThread;
        private final Thread initThread;
        
        public ConnectionHandler(){
            initThread = new Thread(this);
            idleThread = new Thread(new IdleThread());
        }
        
        public void initConnection(){
            setStatus(STATUS_CONNECTING);
            initThread.start();
        }
        
        public void stopListening(boolean value){
            stopListening = value;
        }
                
        public void close(){
            close = true;
        }
        
        @Override
        public void run(){
            do{
                ADBAdapter.exec(ADBAdapter.FORWARD, PORT_ADBCONNECTION);
                try {        
                    initThread.sleep(200);
                } catch (InterruptedException ex) {
                }
            }
            while(!USBDataAdapter.this.initConnection() && !close);
        
            idleThread.start();
            
        }

        /**
         * This thread waits for commands from the other device and handles them
         */
        class IdleThread implements Runnable {
            
            @Override
            public void run() {

                while(!close){
                    if(!stopListening){
                    
                        try{
                        
                            String line = in.readLine();
                            
                            if(line.equals(CMD_CLOSE)               ||
                                    line.equals(CMD_SEND_START)     || 
                                    line.equals(CMD_SEND_FINISH)    || 
                                    line.equals(CMD_RECIEVE)        || 
                                    line.equals(CMD_CONNECTED))
                                System.out.println("Command Recieved: " + line);
                            
                            // check for commands
                            if(line.equals(CMD_SEND_START)) startReadDatabase();
                            if(line.equals(CMD_RECIEVE)) setStatus(STATUS_RECIEVE_REQUEST);
                            if(line.equals(CMD_CONNECTED)) setStatus(STATUS_CONNECTED);
                            if(line.equals(CMD_CLOSE)) {
                                USBDataAdapter.this.close();
                                USBDataAdapter.this.open();
                            }
                            
                        }
                        catch(Exception e){
                        }

                    }
                    try {        
                        idleThread.sleep(200);
                    } catch (InterruptedException ex) {
                    }
                }
                
            }
            
        }
        
    }
    
}